package com.fcat.base.server.util;

import com.jcraft.jsch.*;
import com.fcat.base.common.util.StrUtil;
import com.fcat.base.server.model.*;

import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ShellCommand {

    private String name;
    private String ip;
    private int port;
    private String username;
    private String password;
    private int timeout;

    private Session session = null;
    private Channel channel = null;
    private ChannelShell channelShell;
    private PipedOutputStream pipedOutputStream;
    private PipedInputStream pipedInputStream;
    private BufferedReader bufferedReader;


    public ShellCommand(String name, String ip, String password) {
        this.name = name;
        this.ip = ip;
        this.port = 22;
        this.username = "root";
        this.password = password;
        this.timeout = 600000;
    }
    public ShellCommand(String name, String ip, int port, String username, String password, int timeout) {
        this.name = name;
        this.ip = ip;
        this.port = port;
        this.username = username;
        this.password = password;
        this.timeout = timeout;
    }

    public void closeChannel() {
        if (channel != null) {
            channel.disconnect();
        }
        if (session != null) {
            session.disconnect();
        }
    }
    public void connectChannelSftp() throws JSchException, IOException {
        JSch jsch = new JSch(); // 创建JSch对象
        session = jsch.getSession(username, ip,port); // 根据用户名，主机ip，端口获取一个Session对象
        session.setPassword(password);
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config); // 为Session对象设置properties
        session.setTimeout(timeout); // 设置timeout时间
        session.connect(); // 通过Session建立链接

        channel = session.openChannel("sftp"); // 打开SFTP通道
        channel.connect(); // 建立SFTP通道的连接
    }

    public void sftp(String fileSrcPath,String fileDstPath) throws FileNotFoundException, SftpException {
        ChannelSftp channelSftp = (ChannelSftp) channel;
        FileInputStream inputStream = new FileInputStream(fileSrcPath);
        if (inputStream != null) {
            System.out.println("-------  开始上传："+fileSrcPath+"  =>  "+fileDstPath);
            channelSftp.put(inputStream, fileDstPath);
            System.out.println("-------  上传完毕");
        }
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        closeChannel();
    }

    public void sftpJar(String projectName) {
        try {
            connectChannelSftp();
            String fileSrcPath = System.getProperty("user.dir")+"/"+projectName+"/target/"+projectName+".jar";
            String fileDstPath = "/docker/springboot/"+projectName+"/app/app.jar"; // 目标文件名
            sftp(fileSrcPath,fileDstPath);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void connectChannelShell() throws JSchException, IOException {
        JSch jsch = new JSch(); // 创建JSch对象
        session = jsch.getSession(username, ip, port); // 根据用户名，主机ip，端口获取一个Session对象
        session.setPassword(password); // 设置密码
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config); // 为Session对象设置properties
        session.setTimeout(timeout); // 设置timeout时间
        session.connect(); // 通过Session建立链接
        channelShell = (ChannelShell) session.openChannel("shell");

        pipedInputStream = new PipedInputStream();
        pipedOutputStream = new PipedOutputStream();
        channelShell.setInputStream(new PipedInputStream(pipedOutputStream));
        channelShell.setOutputStream(new PipedOutputStream(pipedInputStream));
        channelShell.connect();
        bufferedReader = new BufferedReader(new InputStreamReader(pipedInputStream));
    }

    public void disconnectChannelShell() throws IOException {
        bufferedReader.close();
        pipedInputStream.close();
        pipedOutputStream.close();
        channelShell.disconnect();
    }

    public void write(String  shellCmd) throws IOException {
        pipedOutputStream.write(shellCmd.getBytes(StandardCharsets.UTF_8));
    }

    public void flush() throws IOException {
        pipedOutputStream.flush();
    }
    public List<String> readLines() throws IOException {
        List<String> lines = new ArrayList<>();
        int i=0;


        Thread thread = new Thread(() -> {
            try{
                String msg = null;
                while((msg = bufferedReader.readLine())!=null){
                    System.out.println(msg);
                    lines.add(msg);
                }
            }catch (InterruptedIOException e){
               // logger.info("中断 bufferedReader.readLine");
            }catch (Exception e){
                e.printStackTrace();
            }
        });
        thread.start();

        int size = lines.size();
        while(i++<5){
            if(size!=lines.size()){
                size = lines.size();
                continue;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        thread.interrupt();

        return lines;
    }
    public void excuteShellCmd(String shellCmd) {
        try {
            connectChannelShell();
            write(shellCmd);
            flush();
            readLines();
            disconnectChannelShell();
            closeChannel();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     Tasks: 190 total,   1 running, 189 sleeping,   0 stopped,   0 zombie
     %Cpu(s):  4.5 us,  4.5 sy,  0.0 ni, 90.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
     KiB Mem : 16266244 total,   239052 free,  9025584 used,  7001608 buff/cache
     KiB Swap:        0 total,        0 free,        0 used.  6900356 avail Mem 

     PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND     
      6123 root      20   0  162124   2240   1540 R   6.2  0.0   0:00.01 top         
      7424 root      20   0 2895640 340344  13920 S   6.2  2.1   3780:14 java        
      7872 root      20   0 4047376 688460  13828 S   6.2  4.2   1570:56 java        
     21427 root      20   0  113364   5692   3516 S   6.2  0.0  34:13.36 containerd+ 


     第一行：
     10:08:45 — 当前系统时间
     10 days, 3:05 — 系统已经运行了10天3小时5分钟（在这期间没有重启过）
     1 users — 当前有1个用户登录系统
     load average: 0.00, 0.00, 0.00 — load average后面的三个数分别是1分钟、5分钟、15分钟的负载情况。

     load average数据是每隔5秒钟检查一次活跃的进程数，然后按特定算法计算出的数值。如果这个数除以逻辑CPU的数量，结果高于5的时候就表明系统在超负荷运转了。

     第二行：
     Tasks — 任务（进程），系统现在共有135个进程，其中处于运行中的有1个，134个在休眠（sleep），stoped状态的有0个，zombie状态（僵尸）的有0个。

     第三行：cpu状态
     0.3% us — 用户空间占用CPU的百分比。
     0.0% sy — 内核空间占用CPU的百分比。
     0.0% ni — 改变过优先级的进程占用CPU的百分比
     99.7% id — 空闲CPU百分比
     0.0% wa — IO等待占用CPU的百分比
     0.0% hi — 硬中断（Hardware IRQ）占用CPU的百分比
     0.0% si — 软中断（Software Interrupts）占用CPU的百分比

     在这里CPU的使用比率和windows概念不同，如果你不理解用户空间和内核空间，需要充充电了。

     第四行：内存状态
     3808060k total — 物理内存总量（4GB）
     3660048k used — 使用中的内存总量（3.6GB）
     148012k free — 空闲内存总量（148M）
     359760k buffers — 缓存的内存量 （359M）

     第五行：swap交换分区
     4184924k total — 交换区总量（4G）
     0k used — 使用的交换区总量（0M）
     4184924k free — 空闲交换区总量（4G）
     2483956k cached — 缓冲的交换区总量（2483M）

     第四行中使用中的内存总量（used）指的是现在系统内核控制的内存数，空闲内存总量（free）是内核还未纳入其管控范围的数量。纳入内核管理的内存不见得都在使用中，还包括过去使用过的现在可以被重复利用的内存，内核并不把这些可被重新使用的内存交还到free中去，因此在linux上free内存会越来越少，但不用为此担心。

     如果出于习惯去计算可用内存数，这里有个近似的计算公式：第四行的free + 第四行的buffers + 第五行的cached，按这个公式此台服务器的可用内存：148M+259M+2483M = 2990M。

     对于内存监控，在top里我们要时刻监控第五行swap交换分区的used，如果这个数值在不断的变化，说明内核在不断进行内存和swap的数据交换，这是真正的内存不够用了。

     第六行是空行

     第七行以下：各进程（任务）的状态监控
     PID — 进程id
     USER — 进程所有者
     PR — 进程优先级
     NI — nice值。负值表示高优先级，正值表示低优先级
     VIRT — 进程使用的虚拟内存总量，单位kb。VIRT=SWAP+RES
     RES — 进程使用的、未被换出的物理内存大小，单位kb。RES=CODE+DATA
     SHR — 共享内存大小，单位kb
     S — 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
     %CPU — 上次更新到现在的CPU时间占用百分比
     %MEM — 进程使用的物理内存百分比
     TIME+ — 进程使用的CPU时间总计，单位1/100秒
     COMMAND — 进程名称（命令名/命令行）
     */
    public Server dealServer() {
        Server server = new Server();
        server.setName(name);
        server.setIp(ip);
        try {
            connectChannelShell();
            List<String> lines;

            write("cd /docker/springboot/ && docker stats --no-stream\r");
            flush();
            lines = readLines();
            List<DockerInfo> dockerInfos = dealDockerInfos(lines);
            server.setDockerInfos(dockerInfos);

            write("df -m\r");
            flush();
            lines = readLines();
            List<SysFile> sysFiles = dealSysFiles(lines);
            server.setSysFiles(sysFiles);


            write("top -n 1\r");
            flush();
            lines = readLines();
            Cpu cpu = dealCpus(lines);
            Mem mem = dealMem(lines);
            server.setCpu(cpu);
            server.setMem(mem);

            disconnectChannelShell();
            closeChannel();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return server;
    }

    public static ShellCommand shellCommandByMeter178(){
        return new ShellCommand("项目一","192.168.9.210","123456");
    }

    public static void main(String[] args) {
        try {
            ShellCommand shellCommand = shellCommandByMeter178();
            Server server = shellCommand.dealServer();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Mem dealMem(List<String> lines) {
        Mem mem = new Mem();
        for(String line:lines) {
            if (line.startsWith("KiB Mem :")) {
                String[] ss = line.split(" ");
                int count = 1;
                for(int i=0;i<ss.length;i++) {
                    String s = ss[i];
                    if (!StrUtil.isBlank(s)) {
                        s = s.replaceAll(" ", "");
                        switch (count++) {
                            case 4:
                                mem.setTotal(Integer.valueOf(s));
                                break;
                            case 6:
                                mem.setFree(Integer.valueOf(s));
                                break;
                            case 8:
                                mem.setUsed(Integer.valueOf(s));
                                break;
                            case 10:
                                mem.setBuff(Integer.valueOf(s));
                                break;
                        }
                    }
                }
                mem.setUsedRate(BigDecimal.valueOf(mem.getUsed()*100).divide(BigDecimal.valueOf(mem.getTotal()),2,BigDecimal.ROUND_HALF_UP).doubleValue());
                break;
            }
        }
        return  mem;
    }

    private static Cpu dealCpus(List<String> lines) {
        Cpu cpu = new Cpu();
        for(String line:lines) {
            if (line.startsWith("%Cpu(s):")) {
                String[] ss = line.split(" ");
                int count = 1;
                for(int i=0;i<ss.length;i++) {
                    String s = ss[i];
                    if (!StrUtil.isBlank(s)) {
                        s = s.replaceAll(" ", "");
                        switch (count++) {
                            case 2:
                                cpu.setUs(Double.valueOf(s));
                                break;
                            case 4:
                                cpu.setSy(Double.valueOf(s));
                                break;
                            case 6:
                                cpu.setNi(Double.valueOf(s));
                                break;
                            case 8:
                                cpu.setId(Double.valueOf(s));
                                break;
                            case 10:
                                cpu.setWa(Double.valueOf(s));
                                break;
                            case 12:
                                cpu.setHi(Double.valueOf(s));
                                break;
                            case 14:
                                cpu.setSi(Double.valueOf(s));
                                break;
                            case 16:
                                cpu.setSt(Double.valueOf(s));
                                break;
                        }
                    }
                }
                cpu.setUsedRate((BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(cpu.getId()))).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue());
                break;
            }
        }

        return  cpu;
    }

    private static List<SysFile> dealSysFiles(List<String> lines) {
        List<SysFile> sysFiles = new ArrayList<>();
        for(String line:lines) {
            if (line.endsWith(" /")) {
                SysFile sysFile = new SysFile();
                sysFiles.add(sysFile);
                String[] ss = line.split(" ");
                int count = 1;
                for(int i=0;i<ss.length;i++) {
                    String s = ss[i];
                    if (!StrUtil.isBlank(s)) {
                        s = s.replaceAll(" ", "");
                        switch (count++) {
                            case 1:
                                sysFile.setFilesystem(s);
                                break;
                            case 2:
                                sysFile.setBlocks(Integer.valueOf(s));
                                break;
                            case 3:
                                sysFile.setUsed(Integer.valueOf(s));
                                break;
                            case 4:
                                sysFile.setAvailable(Integer.valueOf(s));
                                break;
                            case 5:
                                sysFile.setUseRate(Double.valueOf(s.replace("%", "")).doubleValue());
                                break;
                        }
                    }
                }
            }
        }

        return  sysFiles;
    }

    private static List<DockerInfo> dealDockerInfos(List<String> lines) {
        List<DockerInfo> dockerInfos = new ArrayList<>();

        boolean startFlag = false;
        for(String line:lines){
            if(line.contains("CONTAINER ID   NAME")){
                startFlag = true;
                continue;
            }
            if(startFlag){

                DockerInfo dockerInfo = new DockerInfo();
                dockerInfos.add(dockerInfo);
                String[] ss = line.split(" ");
                int count = 1;
                for(int i=0;i<ss.length;i++){
                    String s = ss[i];
                    if(!StrUtil.isBlank(s)){
                        s = s.replaceAll(" ","");
                        switch (count++){
                            case 1:
                                dockerInfo.setContainerId(s);
                                break;
                            case 2:
                                dockerInfo.setName(s);
                                break;
                            case 3:
                                dockerInfo.setCpuUseRate(Double.valueOf(s.replace("%","")).doubleValue());
                                break;
                            case 4:
                                dockerInfo.setMemUsage(s);
                                break;
                            case 6:
                                dockerInfo.setLimit(s);
                                break;
                            case 7:
                                dockerInfo.setMemUseRate(Double.valueOf(s.replace("%","")).doubleValue());
                                break;
                            case 8:
                                dockerInfo.setNeti(s);
                                break;
                            case 10:
                                dockerInfo.setNeto(s);
                                break;
                            case 11:
                                dockerInfo.setBlocki(s);
                                break;
                            case 13:
                                dockerInfo.setBlocko(s);
                                break;
                            case 14:
                                dockerInfo.setPids(Integer.valueOf(s));
                                break;
                        }
                    }
                }

            }
        }
        return dockerInfos;
    }
}